#include "user_socket.h"
#include "log_info.h"

#include <fcntl.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <net/if.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <linux/sockios.h>
#include <sys/ioctl.h>

/* 
 * TCP
 * 传输层协议
 * 有连接
 * 可靠传输
 * 面向字节流
 * 
 * UDP
 * 传输层协议
 * 无连接
 * 不可考传输
 * 面向数据报
 * UDP服务器流程: (接收端)
 * socket() -> bind() -> recvfrom()/sendto() - > close()
 * UDP客户端流程: (发送端)
 * socket() -> recvfrom()/sendto() -> close()
 *
 */


#if 0
static int demo_socket_udp_server_create(){
    int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    int turnon = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void *)&turnon, sizeof(turnon));      /* 设置地址复用 */
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (void *)&turnon, sizeof(turnon));      /* 设置端口复用 */

    int turnoff = 0;
    setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, (void *)&turnoff, sizeof(turnoff)); /* 关闭回环检测 */

    return sockfd;
}


static int demo_socket_udp_seerver_sendto(int sockfd, const char *paddr, int port, const char *data, int size){
    struct sockaddr_in _sockaddr;
    memset(&_sockaddr, 0, sizeof(struct sockaddr_in));
    _sockaddr.sin_family = AF_INET;
    _sockaddr.sin_port = htons(port);
    _sockaddr.sin_addr.s_addr = inet_addr(paddr);
    int ret = sendto(sockfd, data, size, 0, (struct sockaddr *)&_sockaddr, sizeof(_sockaddr));

    return ret;
}


static int demo_socket_udp_client_create(const char *paddr, int port){
    int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    int turnon = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void *)&turnon, sizeof(turnon));      /* 设置地址复用 */
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (void *)&turnon, sizeof(turnon));      /* 设置端口复用 */

    int turnoff = 0;
    setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, (void *)&turnoff, sizeof(turnoff)); /* 关闭回环检测 */


    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr(paddr);
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);
    setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));        /* 加入ip组播地址 */

    struct sockaddr_in _sockaddr_in;
    memset(&_sockaddr_in, 0, sizeof(struct sockaddr_in));
    _sockaddr_in.sin_port = htons(port);
    _sockaddr_in.sin_family = AF_INET;
    _sockaddr_in.sin_addr.s_addr = inet_addr("0.0.0.0");
    bind(sockfd, (struct sockaddr *)&_sockaddr_in, sizeof(struct sockaddr_in));       /* 套接字绑定 */
    
    return sockfd;
}

static int demo_socket_udp_client_recvfrom(int sockfd, char *pbuffer, int maxlen){
    struct sockaddr_in client_addr;
    memset(&client_addr, 0, sizeof(struct sockaddr_in));
    socklen_t adder_len = sizeof(struct sockaddr_in);
    
    int ret = recvfrom(sockfd, pbuffer, maxlen, 0, (struct sockaddr *)&client_addr, (socklen_t *)&adder_len);
    LOG_WHITE("from'%s:%d'\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

    return ret;
}

#define GROUP_IP   "230.0.0.0"
#define GROUP_PORT 8848

static void demo_servers(){
    int sockfd = demo_socket_udp_server_create();
    
    while(1){
        demo_socket_udp_seerver_sendto(sockfd, GROUP_IP, GROUP_PORT, "sh*t bro", 10);
        usleep(1000);
    }

    close(sockfd);
}


static void demo_client(){
    int sockfd = demo_socket_udp_client_create(GROUP_IP, GROUP_PORT);

    char buf[16] = {0};
    while(1){
        demo_socket_udp_client_recvfrom(sockfd, buf, sizeof(buf)-1);
        usleep(1000);
    }

    close(sockfd);

}

#endif




/**
 * @brief  设置阻塞模式
 * @date   2023-10-23 09:35:26
 * @author xiaole
 * @param  fd
 * @param  en   [true]-阻塞  [false]-非阻塞
 * @return int
 * @note
 */
int user_socket_block_enable(int fd, bool en){
    int ret;
    int flags = fcntl(fd, F_GETFL, 0);

    if(flags == -1){
        LOG_ERROR("getfl fail...\b");
        return -1;
    }

    if(en == true){
        flags &= ~O_NONBLOCK;
    }else{
        flags |= O_NONBLOCK;
    }

    ret = fcntl(fd, F_SETFL, flags);
    if(ret == -1){
        LOG_ERROR("setfl fail...\b");
        return -1;
    }

    return 1;
}


/**
 * @brief  
 * @date   2023-10-30 09:31:21
 * @author xiaole
 * @param  sock
 * @param  port
 * @param  addr [0.0.0.0] 监听所有可用的网络接口
 * @return int
 * @note
 */
int user_socket_bind(int sock, int port, const char * addr){
    struct sockaddr_in _addr;
    memset(&_addr, 0, sizeof(struct sockaddr_in));
    _addr.sin_port = htons(port);
    _addr.sin_family = AF_INET;
    _addr.sin_addr.s_addr = inet_addr(addr);
    if (bind(sock, (struct sockaddr *)&_addr, sizeof(struct sockaddr_in)) < 0){
        LOG_ERROR("bind err   port=%d\n", port);
        return -1;
    }

    return 1;
}


/**
 * @brief  用select接口
 * @date   2023-09-19 11:58:52
 * @author xiaole
 * @param  fd
 * @param  timeout_ms   超时时间
 * @param  read_flg     读?
 * @param  write_flg    写?
 * @return int
 * @note   select可以避免程序一直阻塞在某个sock的读写操作上
 */
int user_socket_select(int fd, int timeout_ms, bool read_flg, bool write_flg){
    fd_set fdset_read;
    fd_set fdset_write;
    FD_ZERO(&fdset_read);       /* 将fd_set变量的所有位初始化位0 */
    FD_ZERO(&fdset_write);  
    FD_SET(fd, &fdset_read);    /* 在参数fdset指向的变量中注册文件描述符fd的信息 */
    FD_SET(fd, &fdset_write);

    struct timeval timeout;
    timeout.tv_sec  = timeout_ms / 1000;
    timeout.tv_usec = timeout_ms % 1000 * 1000;

    return select(fd+1, (read_flg ? &fdset_read:NULL), (write_flg ? &fdset_write:NULL), NULL, &timeout);

}


/**
 * @brief  打开创建udp套接字
 * @date   2023-10-31 09:25:51
 * @author xiaole
 * @param  port
 * @return int
 * @note   一对多 多对一
 */
int user_socket_udp_open(int port){

    int sock = user_socket_open(SOCK_DGRAM, IPPROTO_UDP, port);
    if(sock < 0){
        LOG_ERROR("socket open fail\n");
        return -1;
    }




    return sock;
}


/**
 * @brief  打开套接字
 * @date   2023-09-19 12:00:23
 * @author xiaole
 * @param  type     SOCK_STREAM(流格式套接字/面向连接的套接字)     SOCK_DGRAM (数据报套接字/无连接的套接字)
 * @param  protocol IPPROTO_UDP/IPPROTO_TCP 可以直接写0 让程序判断
 * @param  port 制定端口
 * @return int
 * @note
 */
int user_socket_open(int type, int protocol, int port){
    int sock = socket(AF_INET, type, protocol);
    if (sock < 0){
        LOG_ERROR("fail...\n");
        return -1;
    }

    
    int optval = 1;

    /* 允许套接字在程序关闭后立即重新使用相同的 IP 地址和端口 */
    if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&optval, sizeof(optval)) < 0){
        LOG_ERROR("SO_REUSEADDR FAIL...\n");
    }

    /* 允许多个套接字绑定到同一个 IP 地址和端口上 */
    if(setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (void *)&optval, sizeof(optval)) < 0){
        LOG_ERROR("SO_REUSEPORT FAIL...\n");
    }  

    return sock;
}


/**
 * @brief  设置套接字在接收组播数据包时是否进行环回检测.
 * @date   2023-10-20 08:11:03
 * @author xiaole
 * @param  sock
 * @param  en[0] 表示禁用环回检测;
 * @param  en[1] 表示启用环回检测;
 * @return int
 * @note   环回检测的目的是为了确保套接字不会收到自己发送的数据包
 */
int user_socket_set_ip_multicast_loop(int sock, int en){
    if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &en, sizeof(en)) < 0){
        LOG_ERROR("IP_MULTICAST_LOOP FAIL... \n");
        return -1;
    }
    return 1;
}

/**
 * @brief  启用或禁用套接字的广播功能
 * @date   2023-10-20 08:21:57
 * @author xiaole
 * @param  sock
 * @param  en
 * @return int
 * @note   广播是一种向本地网络上所有设备发送消息的方式.启用广播后,套接字可以发送和接收广播消息.
 */
int user_socket_set_so_broadcast(int sock, int en){
    if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (const char *)&en, sizeof(en)) < 0){
        LOG_ERROR("SO_BROADCAST FAIL...\n");
        return -1;
    }
    return -1;
}

/**
 * @brief  将套接字绑定到指定的网络设备
 * @date   2023-10-19 11:51:39
 * @author xiaole
 * @param  sock_fd
 * @param  name
 * @return int
 * @note   确保数据流只能通过该设备进行通信.它的主要作用是在具有多个网络接口的主机上实现网络流量的控制和管理
 */
int user_socket_set_bindtodevice(int sock_fd, const char*name){
    struct ifreq ifr;
	memset(&ifr, 0x00, sizeof(struct ifreq));
    strncpy(ifr.ifr_name, name, strlen(name));

    if (setsockopt(sock_fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(struct ifreq)) < 0){
        LOG_ERROR("SO_BINDTODEVICE FAIL... \n");
        return -1;
    }
    return 1;
}


/**
 * @brief  
 * @date   2023-10-20 08:17:05
 * @author xiaole
 * @param  sock_fd
 * @param  name
 * @return int
 * @note   用于设置多播套接字的网络接口.它允许应用程序指定哪个网络接口应用于多播通信
 */
int user_socket_set_ip_multicast_if(int sock_fd, const char *name){
    struct ifreq ifr;
    strncpy(ifr.ifr_name, name, sizeof(struct ifreq));
    if (setsockopt(sock_fd, IPPROTO_IP, IP_MULTICAST_IF, &ifr, sizeof(struct ifreq)) < 0) {
        LOG_ERROR("IP_MULTICAST_IF FAIL...\n");
        return -1;
    }


    return 1;
}

/**
 * @brief  加入一个ip组播
 * @date   2023-09-18 17:30:40
 * @author xiaole
 * @param  sock_fd
 * @param  addr
 * @return int
 * @note   当一个套接字希望接收特定多播组的数据时,需要使用这个选项来加入该多播组
 */
int user_socket_set_ip_add_memership(int sock_fd, const char *addr){
    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = inet_addr(addr);
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);

    if (setsockopt(sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0){
        LOG_ERROR("IP_ADD_MEMBERSHIP FAIL...\n");
        return -1;
    }
    return 0;
}













/**
 * @brief  打开tcp
 * @date   2023-09-20 10:46:34
 * @author xiaole
 * @param  port
 * @return int
 * @note   一对一
 */
int user_socket_tcp_open(int port){

    int sock = user_socket_open(SOCK_STREAM, IPPROTO_TCP, port);
    if(sock < 0){
        LOG_ERROR("socket open fail\n");
        return -1;
    }

    return sock;
}



/**
 * @brief  tcp连接
 * @date   2023-09-20 10:46:24
 * @author xiaole
 * @param  socket_fd
 * @param  ip
 * @param  port
 * @return int
 * @note
 */
int user_socket_tcp_connect(int socket_fd, const char *ip, int port, int timeout_ms){

    port = port<=0 ? 80:port;

    user_socket_block_enable(socket_fd, false);


    struct sockaddr_in ser_addr;
    memset(&ser_addr, 0, sizeof(ser_addr));
    
    ser_addr.sin_family = AF_INET;
    ser_addr.sin_port = htons(port);
    ser_addr.sin_addr.s_addr = inet_addr(ip);
    int ret = connect(socket_fd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));
    if((ret<0) && (errno == EINPROGRESS)){
        LOG_WHITE("connect(%s:%d) success \n", ip, port);
        fd_set fdset;
        FD_ZERO(&fdset);
        FD_SET(socket_fd, &fdset);

        struct timeval timeout;
        timeout.tv_sec  = timeout_ms / 1000;
        timeout.tv_usec = timeout_ms % 1000 * 1000;

        int reslut = 0;
        do{
            reslut = select(socket_fd + 1, NULL, &fdset, NULL, &timeout);
        } while (reslut < 0 && errno == EINTR);

        if(reslut <= 0){
            LOG_ERROR("SELECT FAIL...\n");
        }

        if(reslut == 1){
            int err;
            socklen_t socklen = sizeof(err);
            int sockoptret = getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, &err, &socklen);
            if(sockoptret == -1) {
                LOG_ERROR("time out\n");
                return -1;
            }

            if(err == 0){
                user_socket_block_enable(socket_fd, true);
                // LOG_WHITE("success\n");
                return 1;
            }else{
                LOG_ERROR("timeout\n");
                return -1;
            }
        }
    }else{
    
        LOG_ERROR("connect(%s:%d) fail... \n", ip, port);
        return -1;
    }
    return 0;
}





/**
 * @brief  udo 发送数据
 * @date   2023-09-19 11:49:03
 * @author xiaole
 * @param  sockfd
 * @param  data 发送数据
 * @param  ip   接收地址
 * @param  port 端口
 * @param  timeout_ms   超时时间
 * @return int
 * @note
 */
int user_socket_sendto(int sockfd, const void *data, int len,const char *ip, int port, int timeout_ms){

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip);


    if(user_socket_select(sockfd, timeout_ms, 0, 1)){
        if (sendto(sockfd, data, len, 0, (struct sockaddr *)&addr, sizeof(addr)) < 0){
            LOG_ERROR("failed...\n");
            return -1;
        }

    }else{
        LOG_ERROR("SELECT FAIL...\n");
        return -1;
    }
    return 0;
}


/**
 * @brief  接收数据
 * @date   2023-09-19 11:49:37
 * @author xiaole
 * @param  sockfd
 * @param  pbuffer  存放的buffer
 * @param  maxLen   读取最大长度
 * @param  timeout_ms   超时时间
 * @return int
 * @note   recvfrom可以同时应用于面向连接的和无连接的套接字
 */
int user_socket_recvfrom(int sockfd, char *pbuffer, int maxLen, int timeout_ms, char *out_addr){

    if (user_socket_select(sockfd, timeout_ms, 1, 0)){
        struct sockaddr_in client_addr;
        memset(&client_addr, 0, sizeof(struct sockaddr_in));
        socklen_t adder_len = sizeof(struct sockaddr_in);
        
        int ret = recvfrom(sockfd, pbuffer, maxLen, 0, (struct sockaddr *)&client_addr, (socklen_t *)&adder_len);

        if(out_addr != NULL) snprintf(out_addr, 19, "%s", inet_ntoa(client_addr.sin_addr));
        // LOG_WHITE("from'%s:%d'\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        return ret;       

    }else{
        // LOG_ERROR("select fail..\n");
    }

    return -1;
}


/**
 * @brief  tcp发送数据
 * @date   2023-09-20 10:46:07
 * @author xiaole
 * @param  socket_fd
 * @param  data
 * @param  timeout_ms
 * @return int
 * @note
 */
int user_socket_send(int sock, const void *data, int timeout_ms){
    if(user_socket_select(sock, timeout_ms, 0, 1)){
        int ret = send(sock, data, strlen(data), MSG_NOSIGNAL);
        if(ret == -1){
            LOG_ERROR("send fail...\n");
            return -1;
        }

    }

    return 1;
}


/**
 * @brief  接收数据
 * @date   2023-09-20 10:45:43
 * @author xiaole
 * @param  sock
 * @param  buffer
 * @param  maxLen   最大长度
 * @param  timeout_ms
 * @return [int] read len
 * @note   recv主要用于面向连接的套接字
 */
int user_socket_recv(int sock, char *buffer, int maxLen, int timeout_ms){

    int read_len = 0;
    int ret = 0;

    do{
        if(user_socket_select(sock, timeout_ms, 1, 0)){
            ret = recv(sock, &buffer[read_len], maxLen, 0);
            read_len += ret;
            maxLen -= read_len;
        }else{
            break;
        }

    
    } while ((1500 <= ret)  && (0 < maxLen));       /* 如果当前读取的数据大于单次读取的最大长度, 可以续读 */


    // LOG_WHITE("ret=%d, readLen=%d\n", ret, read_len);

    return read_len;
}

/**
 * @brief  获取网络名对应的ip mask gateway
 * @date   2023-10-31 09:02:34
 * @author xiaole
 * @param  nwkname
 * @param  p_ip
 * @param  p_mask
 * @param  p_gateway
 * @return int
 * @note
 */
int user_socket_network_card_get_ip(const char *nwkname, char *p_ip, char *p_mask, char *p_gateway){
    int sock_fd;
    struct ifreq ifr;
    struct sockaddr_in *sin_ptr;

    // 创建socket
    sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd == -1) {
        perror("socket");
        return -1;
    }


    
    // 查询IP地址
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, nwkname, strlen(nwkname));
    if (ioctl(sock_fd, SIOCGIFADDR, &ifr) == -1) {
        LOG_ERROR("SIOCGIFADDR FAIL...\n");
        return -1;
    }
    sin_ptr = (struct sockaddr_in *)&(ifr.ifr_addr);
    if(p_ip) strncpy(p_ip, inet_ntoa(sin_ptr->sin_addr), 16);


    // 查询子网掩码
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, nwkname, strlen(nwkname));
    if (ioctl(sock_fd, SIOCGIFNETMASK, &ifr) == -1) {
        perror("ioctl");
        return -1;
    }
    sin_ptr = (struct sockaddr_in *)&(ifr.ifr_addr);
    if(p_mask) strncpy(p_mask, inet_ntoa(sin_ptr->sin_addr), 16);



    // 查询网关
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, nwkname, strlen(nwkname));
    if (ioctl(sock_fd, SIOCGIFBRDADDR, &ifr) == -1) {
        perror("ioctl");
        return -1;
    }
    sin_ptr = (struct sockaddr_in *)&(ifr.ifr_broadaddr);
    if(p_gateway) strncpy(p_gateway, inet_ntoa(sin_ptr->sin_addr), 16);

    close(sock_fd);
    return 1;
}


/**
 * @brief  设置套接字接收缓冲区大小
 * @date   2023-10-30 15:19:55
 * @author xiaole
 * @param  sockfd
 * @param  size
 * @return int
 * @note
 */
int user_socket_set_so_rcvbuf(int sockfd, unsigned int size){
    int rcvbuf_size = size;
    if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf_size, sizeof(rcvbuf_size)) < 0) {
        perror("setsockopt");
        exit(1);
    }

    return 1;

}

/**
 * @brief  设置套接字发送缓冲区大小
 * @date   2023-10-30 16:56:55
 * @author xiaole
 * @param  sockfd
 * @param  size
 * @return int
 * @note
 */
int user_socket_set_so_sndbuf(int sockfd, unsigned int size){
    int sndbuf_size = size;
    if (setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sndbuf_size, sizeof(sndbuf_size)) < 0) {
        LOG_ERROR("SET SO_SNDBUF FAIL...\n");
        return -1;
    }

    return 1;

}



/**
 * @brief  手动清除接收缓存区数据, 以接收最新数据
 * @date   2023-11-01 16:16:59
 * @author xiaole
 * @param  sockfd
 * @param  size
 * @return int
 * @note   有时候由于系统的调用, 或打开一个比较耗时的接口, 会造成数据延迟, 这里就需要手动清除一下数据大小, 先设置为0, 然后在重新指定大小
 */
int user_socket_rcvbuf_clear(int sockfd){


    user_socket_block_enable(sockfd, false);
    user_socket_set_so_rcvbuf(sockfd, 0);
    
    char buf[10240] = {0};
    int ret = user_socket_recv(sockfd, buf, sizeof(buf), 50);
    LOG_YELLOW("ret=%d\n", ret);
    
    user_socket_block_enable(sockfd, false);



    return 1;
}


